" exjava.vim -- Manage java imports
" Author: Ding Liu <18123715300@163.com>
" Requires: Vim-7.0 or higher
" Version: 1.0
" Licence: This program is free software; you can redistribute it and/or
"          modify it under the terms of the GNU General Public License.
"          See http://www.gnu.org/copyleft/gpl.txt
" Summary Of Features:
"   Insert import
"   Sort And Distinct imports
"   Reset Unused imports
" Usage:
"   Copy this file in your vim plugin folder
"   No classpath or configuration needed. This plugin use the regular vim search.
"   So you only need a good search index (through ctags or cscope for example)
" Commands:

" variables {{{
if exists("g:loaded_sortimport") || &cp
    finish
endif

if exists('g:loaded_unused_imports')
    finish
endif

let g:loaded_sortimport= 1
let g:loaded_unused_imports = '1'

if !exists('g:sortedPackage')
    let g:sortedPackage = ["java", "javax", "org", "com"]
endif

if !exists('g:packageSepDepth') || g:packageSepDepth < 1
    let g:packageSepDepth = 2
endif

if !exists('g:autowired_dict_s')
    let g:autowired_dict_s = {'dao': 'Dao', 'mybatis': 'MybatisDao', 'repository': 'Repository', 'mapper': 'Mapper', 'mapstruct': 'BeanMapper'}
endif

if !exists('g:autowired_dict_c')
    let g:autowired_dict_c = {'service': 'Service'}
endif

let s:title = "-JavaSR-"

let s:zoom_in = 0
let s:keymap = {}

let s:help_open = 0
let s:help_text_short = [
            \ '" Press <F1> for help',
            \ '',
            \ ]
let s:help_text = s:help_text_short

let s:last_record_line = 0
let s:last_record_col = 0

let s:matches_so_far = []
let s:vimporter_global_lookup = {}
let s:importPattern = '^\s*import\s\+.*;\s*$'
" }}}

" function exjava#bind_mappings {{{
function exjava#bind_mappings()
    call ex#keymap#bind( s:keymap )
endfunction
" }}}

" function exjava#register_hotkey {{{
function exjava#register_hotkey( priority, local, key, action, desc )
    call ex#keymap#register( s:keymap, a:priority, a:local, a:key, a:action, a:desc )
endfunction
" }}}

" function s:update_help_text {{{
function s:update_help_text()
    if s:help_open
        let s:help_text = ex#keymap#helptext(s:keymap)
    else
        let s:help_text = s:help_text_short
    endif
endfunction
" }}}

" function exjava#toggle_help() {{{
function exjava#toggle_help()
    if !g:ex_java_enable_help
        return
    endif

    let s:help_open = !s:help_open
    silent exec '1,' . len(s:help_text) . 'd _'
    call s:update_help_text()
    silent call append ( 0, s:help_text )
    silent exec 'normal! dd'
    silent keepjumps normal! gg
    call ex#hl#clear_confirm()
endfunction
" }}}

" function exjava#open_window {{{
function exjava#init_buffer()
    set filetype=exjava

    augroup exjava
        au! BufWinLeave <buffer> call <SID>on_close()
    augroup END

    " if line('$') <= 1 && g:ex_java_enable_help
        " silent call append ( 0, s:help_text )
        " silent exec '$d'
    " endif
endfunction
" }}}

" function s:on_close() {{{
function s:on_close()
    let s:zoom_in = 0
    let s:help_open = 0
    let s:last_record_line = line('.')
    let s:last_record_col = col('.')

    " go back to edit buffer
    call ex#window#goto_edit_window()
    " call ex#hl#clear_target()
endfunction
" }}}

" function exjava#open_window() {{{
function exjava#open_window()
    let winnr = winnr()
    if ex#window#check_if_autoclose(winnr)
        call ex#window#close(winnr)
    endif
    call ex#window#goto_edit_window()

    " 关闭冲突的插件窗口
    let winnr = bufwinnr(s:title)
    let mogwinnr = bufwinnr("-MongoDB-")
    let sqlwinnr = bufwinnr("-MySQL-")
    let qfwinnr = bufwinnr("-QFix-")
    let undotreewinnr = bufwinnr("undotree_0")
    let diffpanelwinnr = bufwinnr("diffpanel_0")

    if mogwinnr != -1
        call ex#window#close(mogwinnr)
    endif
    if sqlwinnr != -1
        call ex#window#close(sqlwinnr)
    endif
    if qfwinnr != -1
        call ex#window#close(qfwinnr)
    endif
    if undotreewinnr != -1 && diffpanelwinnr != -1 && exists ( ':UndotreeHide' )
        silent exec "UndotreeHide"
    endif

    if winnr == -1
        call ex#window#open(
                    \ s:title,
                    \ g:ex_java_winsize,
                    \ g:ex_java_winpos,
                    \ 1,
                    \ 1,
                    \ function('exjava#init_buffer')
                    \ )
    else
        exe winnr . 'wincmd w'
    endif

    if !s:last_record_line && !s:last_record_col
        silent execute 'normal! G'
    else
        silent call cursor(s:last_record_line, s:last_record_col)
    endif
endfunction
" }}}

" function exjava#toggle_window {{{
function exjava#toggle_window()
    let result = exjava#close_window()
    if result == 0
        call exjava#open_window()
    endif
endfunction
" }}}

" function exjava#close_window {{{
function exjava#close_window()
    let winnr = bufwinnr(s:title)
    if winnr != -1
        call ex#window#close(winnr)
        return 1
    endif
    return 0
endfunction
" }}}

" function exjava#toggle_zoom {{{
function exjava#toggle_zoom()
    let winnr = bufwinnr(s:title)
    if winnr != -1
        if s:zoom_in == 0
            let s:zoom_in = 1
            call ex#window#resize( winnr, g:ex_java_winpos, g:ex_java_winsize_zoom )
        else
            let s:zoom_in = 0
            call ex#window#resize( winnr, g:ex_java_winpos, g:ex_java_winsize )
        endif
    endif
endfunction
" }}}

" function exjava#JavaSortImport() {{{
function exjava#JavaSortImport()
    " save current position to set it back later
    let startLine = line(".")
    let startCol = col(".")

    1
    let firstLine = search(s:importPattern, 'cW')
    if firstLine > 0
        normal! G
        let lastLineBSort = search(s:importPattern, 'cbW')

        let i = lastLineBSort
        while i >= firstLine
            let line = getline(i)
            let lis = matchlist(line, '\v^\s*import\s+(\w+\.)+(\w+)\s*;\s*$')
            if len(lis) == 0
                let lis = matchlist(line, '\v^\s*import static\s+(\w+\.)+(\w+)\s*;\s*$')
            endif
            if len(lis) > 0
                let s = lis[2]
                let searchPatternA = '\v(//.*)@<!<' . s . '>'
                let searchPatternB = '^\s*//\s*import\s\+.*;$'

                let j = lastLineBSort + 1
                call cursor(j, 1)
                let linefoundA = search(searchPatternA, 'cnW')
                if linefoundA == 0
                    exec i . 'd _'
                    if startLine > i
                        let startLine -= 1
                    endif
                    let i -= 1
                    let lastLineBSort -= 1
                    continue
                endif

                call cursor(lastLineBSort, 1)
                let linefoundB = search(searchPatternB, 'cbnW')
                if linefoundB != 0
                    exec linefoundB . 'd _'
                    if startLine > linefoundB
                        let startLine -= 1
                    endif
                    let i -= 1
                    let lastLineBSort -= 1
                    if linefoundB >= i
                        continue
                    endif
                endif
            endif
            let i -= 1
        endwhile

        if lastLineBSort >= firstLine
            exe "" . firstLine . "," . lastLineBSort . "sort u"
            if getline(".") =~ "^\s*$"
                delete
            endif
            normal! G
            let lastLineASort = search(s:importPattern, 'cbW')
            if lastLineASort < lastLineBSort
                if startLine > lastLineASort
                    let startLine -= lastLineBSort - lastLineASort
                endif
            endif
            1
            for name in g:sortedPackage
                let pattern = '^\s*import\s\+' . name . '\..*;\s*$'
                let firstPackageLine = search(pattern, 'cW')
                if firstPackageLine > 0
                    let @a = ""
                    exe 'g/' . pattern . '/d A'
                    exe firstLine
                    normal! "aPddG
                    exe search(pattern, 'cbW')
                    normal! j
                    let firstLine = line(".")
                endif
            endfor
            1
            if (g:packageSepDepth > 0)
                while search(s:importPattern, 'W') > 0
                    let curLine = getline(".")
                    let curMatch = substitute(curLine, '\(^\s*import\s\+\(\.\?[^\.]\+\)\{0,' . g:packageSepDepth . '\}\).*', '\1', "")
                    if (curMatch == curLine)
                        let curMatch = substitute(curMatch, '\(.*\)\..*', '\1', "")
                    endif
                    while match(getline("."), curMatch) >= 0
                        normal! j
                    endwhile
                    normal! O
                    if startLine >= line(".")
                        let startLine += 1
                    endif
                endwhile
            endif
            if getline(".") =~ "^\s*$"
                delete
                if startLine > line(".")
                    let startLine -= 1
                endif
            endif
        endif
    endif

    if startLine > 0 && startLine > line(".")
        " set cursor back to initial position
        call cursor(startLine, startCol)
    endif
    silent execute 'normal! zz'
endfun
" }}}

" exjava#confirm_select {{{
function exjava#confirm_select()
    let linestr = getline(".")

    call exjava#close_window()
    call ex#window#goto_edit_window()

    let startLine = line(".")
    let startCol = col(".")
    let curMatchDepth = substitute(linestr, '\(^\s*\(\.\?\w\+\)\{0,' . g:packageSepDepth . '\}\).*$', '\1', "")
    let curMatchBasic = substitute(linestr, '\(^\s*\w\+\)\..*$', '\1', "")
    if curMatchDepth ==# linestr
        let curMatchDepth = curMatchBasic
    endif
    let curMatchDepth = substitute(curMatchDepth, '\.', '\\\.', 'g')
    let patternA = '^\s*import\s\+' . curMatchDepth . '\..*;\s*$'
    let patternB = '^\s*import\s\+' . curMatchBasic . '\..*;\s*$'

    normal! G
    if search(patternA, 'cbW') > 0
        call append(line("."), 'import ' . linestr . ';')
        if startLine > line(".")
            let startLine += 1
        endif
    elseif search(patternB, 'cbW') > 0
        call append(line("."), 'import ' . linestr . ';')
        call append(line("."), "")
        if startLine > line(".")
            let startLine += 2
        endif
    elseif search('^\s*import\s\+\(\w\+\.\)\+\(\w\+\)\s*;\s*$', 'cbW') > 0
        call append(line("."), "import " . linestr . ';')
        call append(line("."), "")
        if startLine > line(".")
            let startLine += 2
        endif
    elseif search('^\s*import\s\+static\s\+\(\w\+\.\)\+\(\w\+\)\s*;\s*$') > 0
        normal! O
        call append(line("."), "")
        call append(line("."), "import " . linestr . ';')
        if startLine > line(".")
            let startLine += 2
        endif
        silent exe line(".") . 'd _'
    elseif search('^\s*package\s\+.*$') > 0
        call append(line("."), "import " . linestr . ';')
        call append(line("."), "")
        if startLine > line(".")
            let startLine += 2
        endif
    else
        1
        normal! O
        call append(line("."), "")
        call append(line("."), "import " . linestr . ';')
        let startLine += 2
        silent exe '1d _'
    endif

    " set cursor back to initial position
    call cursor(startLine, startCol)
    " call exjava#JavaSortImport()
    silent execute 'normal! zz'
endfunction
" }}}

" function exjava#Vimporter_JavaLib(word) {{{
function exjava#Vimporter_JavaLib(word)
    if exists('g:exvim_project_root') && exists('g:exvim_project_name') && exists('g:ex_java_search_path')
        let files = []
        let jre_file = expand(g:ex_java_search_path).'/jre.class.list'
        let deps_file = g:exvim_project_root.'/.exvim.'.g:exvim_project_name.'/deps.class.list'

        if filereadable(jre_file)
            let files = add(files, jre_file)
        endif
        if filereadable(deps_file)
            let files = add(files, deps_file)
        endif

        " Clear out the lookup dict
        let s:vimporter_global_lookup = {}
        if isdirectory(g:exvim_project_root)
            call exjava#SetDisplayNameInMap(exjava#RetrieveFromDir(g:exvim_project_root, a:word), g:exvim_project_root)
        endif
        if !empty(files)
            call exjava#SetDisplayNameInMap(exjava#RetrieveFromFile(a:word, files))
        endif

        if empty(s:vimporter_global_lookup)
            call ex#warning( "Couldn't find: " . a:word )
        else
            call exjava#open_window()
            " clear contents
            silent exec '1,$d _'

            " add online help
            " if g:ex_java_enable_help
            " silent call append( 0, s:help_text )
            " silent exec '$d'
            " endif

            silent call append( 0, exjava#DisplayList() )
            silent exec 'normal! dd'
            silent exec 'normal! zb'

            if len(keys(s:vimporter_global_lookup)) == 1
                call exjava#confirm_select()
            endif
        endif
    else
        silent echohl WarningMsg
        echomsg "Not exvim project"
        silent echohl None
    endif
endfunction
" }}}

" function exjava#RetrieveFromDir(doc, word) {{{
function exjava#RetrieveFromDir(doc, word)
    let find_cmd = "find " . a:doc . " -type f"
                \ . " -name '" . a:word . ".java'"
                \ . " -o -name '" . a:word . ".class'"
                \ . " -o -name '*$" . a:word . ".class'"

    return system(find_cmd)
endfunction
" }}}

" function exjava#RetrieveFromFile(word, files) {{{
function exjava#RetrieveFromFile(word, files)
    return system("grep -h '^.*\\." . a:word . "$' " . join(a:files))
endfunction
" }}}

" function exjava#SetDisplayNameInMap(text, ...) {{{
function exjava#SetDisplayNameInMap(text, ...)
    if a:text !=# ''
        for import_line in split(a:text, "\n")
            if a:0 >= 1
                let line = exjava#TrimWhitespace(import_line)
                let line = substitute(line, '^'.substitute(a:1, '\.', '\\\.', 'g').'/\?\(.*\)$', '\1', '')
                let line = substitute(line, '^.*\(\(src/main/java/\)\|\(src/test/java/\)\|\(target/classes/\)\)\(.*\)$', '\5', '')
                let line = substitute(line, '^\(.*\)\..*$', '\1', '')
                let line = substitute(line, '/\|\$', '.', 'g')
            else
                let line = import_line
            endif
            let s:vimporter_global_lookup[line] = ''
        endfor
    endif
endfunction
" }}}

" function exjava#DisplayList() {{{
function exjava#DisplayList()
    return sort(keys(s:vimporter_global_lookup))
endfunction
" }}}

" function exjava#TrimWhitespace(input_string) {{{
function exjava#TrimWhitespace(input_string)
    return substitute(a:input_string, '^\s*\(.*\)\s*$', '\1', '')
endfunction
" }}}

" function exjava#LittlelotAutoGen() {{{
function exjava#LittlelotAutoGen()
    let lastLineNo = line('$')
    if lastLineNo != 1 || len(getline(lastLineNo)) != 0
        return
    endif

    call exjava#LittlelotGenPackage()
    call exjava#LittlelotGenClass()
endfunction
" }}}

" function exjava#LittlelotGenPackage() {{{
function exjava#LittlelotGenPackage()
    if match(expand("%:h"), '/\?src/main/java/\?') != -1
        let s:curr_package = substitute( substitute(expand("%:h"), '^.*/\?src/main/java/\?', '', ''), '/', '\.', 'g' )
    elseif match(expand("%:h"), '/\?src/test/java/\?') != -1
        let s:curr_package = substitute( substitute(expand("%:h"), '^.*/\?src/test/java/\?', '', ''), '/', '\.', 'g' )
    else
        let s:curr_package = ""
    endif

    let s:curr_class = substitute(expand("%:t"), '\..*$', '', '')
endfunction
" }}}

" function exjava#LittlelotGenClass() {{{
function exjava#LittlelotGenClass()
    let s:package_line = (s:curr_package !=# "" ? "package " . s:curr_package . ";\n\n" : "")
    let s:author_line = "/**\n * @author littlelot\n */\n"
    let s:curr_class_type = "class"
    let s:import_line = ""
    let s:annotate_line = ""
    let s:extends_line = ""
    let s:implements_line = ""
    let s:fields_line = ""

    if match(s:curr_class, 'Service$') > 0 || match(s:curr_class, 'Mapper$') > 0
                \ || match(s:curr_class, 'Dao$') > 0 || match(s:curr_class, 'Repository$') > 0
        let s:curr_class_type = "interface"
    endif

    if match(s:curr_class, 'BeanMapper$') > 0
        let s:import_line = s:import_line . "import org.mapstruct.Mapper;\n\n"
        let s:annotate_line = s:annotate_line . "@Mapper(componentModel=\"spring\")\n"
    elseif match(s:curr_class, 'Mapper$') > 0 || match(s:curr_class, 'MybatisDao$') > 0
        let s:import_line = s:import_line . "import org.apache.ibatis.annotations.Mapper;\n\n"
        let s:annotate_line = s:annotate_line . "@Mapper\n"
    elseif match(s:curr_class, 'Dao$') > 0 || match(s:curr_class, 'Repository$') > 0
        if match(s:curr_class, "Repository$") > 0
            let curr_domain = substitute(s:curr_class, 'Repository$', '', '')
        else
            let curr_domain = substitute(s:curr_class, 'Dao$', '', '')
        endif

        let s:import_line = s:import_line . "import org.springframework.data.jpa.repository.JpaRepository;\n"
                    \ . "import " . substitute(s:curr_package, 'dao\|repository$', 'domain', '') . "." . curr_domain . ";\n\n"
        let s:extends_line = s:extends_line . "extends JpaRepository<" . curr_domain . ", String> "
    elseif match(s:curr_class, 'ServiceImpl$') > 0
        let curr_service = substitute(s:curr_class, 'Impl$', '', '')
        let curr_domain = substitute(s:curr_class, 'ServiceImpl$', '', '')

        let s:import_line = s:import_line . "import org.springframework.stereotype.Service;\n"
                    \ . "import org.springframework.beans.factory.annotation.Autowired;\n"
                    \ . "import " . substitute(s:curr_package, '\.impl$', '', '') . "." . curr_service . ";\n"
        for key in keys(g:autowired_dict_s)
            let s:import_line = s:import_line . "import "
                        \ . substitute(s:curr_package, '\.service\.impl$', '', '')
                        \ . "." . key . "." . curr_domain . g:autowired_dict_s[key] . ";\n"
            let s:fields_line = s:fields_line . "\n\t@Autowired\n"
                        \ . "\tprivate " . curr_domain . g:autowired_dict_s[key] . " "
                        \ . substitute(curr_domain, '.*', '\l\0', '') . g:autowired_dict_s[key] . ";\n"
        endfor
        let s:import_line = s:import_line . "\n"

        let s:annotate_line = s:annotate_line . "@Service\n"
        let s:implements_line = s:implements_line . "implements " . curr_service . " "
    elseif match(s:curr_class, 'Controller$') > 0
        let curr_domain = substitute(s:curr_class, 'Controller$', '', '')

        let s:import_line = s:import_line . "import org.springframework.web.bind.annotation.RestController;\n"
                    \ . "import org.springframework.web.bind.annotation.RequestMapping;\n"
                    \ . "import org.springframework.beans.factory.annotation.Autowired;\n"
                    \ . "import io.swagger.annotations.Api;\n"
        for key in keys(g:autowired_dict_c)
            let s:import_line = s:import_line . "import "
                        \ . substitute(s:curr_package, '\.controller$', '', '')
                        \ . "." . key . "." . curr_domain . g:autowired_dict_c[key] . ";\n"
            let s:fields_line = s:fields_line . "\n\t@Autowired\n"
                        \ . "\tprivate " . curr_domain . g:autowired_dict_c[key] . " "
                        \ . substitute(curr_domain, '.*', '\l\0', '') . g:autowired_dict_c[key] . ";\n"
        endfor
        let s:import_line = s:import_line . "\n"

        let s:annotate_line = s:annotate_line . "@RestController\n"
                    \ . "@RequestMapping(\"/\")\n"
                    \ . "@Api(value=\"\",tags=\"\")\n"
    elseif match(s:curr_package, 'domain$') > 0
        let s:import_line = s:import_line . "import javax.persistence.Entity;\n"
                    \ . "import javax.persistence.Table;\n"
                    \ . "import io.swagger.annotations.ApiModel;\n"
                    \ . "import lombok.Data;\n\n"
        let s:annotate_line = s:annotate_line . "@Data\n@Entity\n@ApiModel(\"\")\n@Table(name=\"\")\n"
    elseif match(s:curr_package, 'vo$') > 0 || match(s:curr_package, 'pojo$') > 0
        let s:import_line = s:import_line . "import lombok.NoArgsConstructor;\n"
                    \ . "import lombok.Data;\n\n"
        let s:annotate_line = s:annotate_line . "@Data\n@NoArgsConstructor\n"
    endif

    let s:template_str = s:package_line . s:import_line . s:author_line . s:annotate_line
                \ . "public " . s:curr_class_type . " " . s:curr_class . " " . s:extends_line
                \ . s:implements_line . "{\n" . s:fields_line . "}"

    call exjava#appendContent(s:template_str)
endfunction
" }}}

" function exjava#appendContent(content) {{{
function exjava#appendContent(content)
    try
        call append(line('.'), split(a:content, '\n'))
        silent exec 'normal dd'
    catch /.*/
    endtry
endfunction
" }}}

" function exjava#RemoveSpaceOrTabOrEnter() {{{
function exjava#RemoveSpaceOrTabOrEnter(pat)
    let c = nr2char(getchar(0))
    return (c =~ a:pat) ? '' : c
endfunction
" }}}

" vim:ts=4:sw=4:sts=4 et fdm=marker:
